package ltd.jdsoft.cute.channel.selector;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.util.CollectionUtils;

import ltd.jdsoft.cute.cache.CacheClient;
import ltd.jdsoft.cute.cache.CacheKeyUtil;
import ltd.jdsoft.cute.cache.callback.GetCallback;
import ltd.jdsoft.cute.channel.exception.ChannelExceptionEnum;
import ltd.jdsoft.cute.channel.model.Channel;
import ltd.jdsoft.cute.channel.model.OrgChannelConfig;
import ltd.jdsoft.cute.channel.model.condition.ChannelCondition;
import ltd.jdsoft.cute.channel.selector.ChannelWeightProcessor.ChannelWeight;
import ltd.jdsoft.cute.channel.service.ChannelService;
import ltd.jdsoft.cute.channel.service.OrgChannelConfigService;
import ltd.jdsoft.cute.common.extention.ExtensionLoader;
import ltd.jdsoft.cute.common.util.StatusType;
import ltd.jdsoft.cute.spring.base.context.ApplicationContextHolder;
import ltd.jdsoft.cute.spring.web.exception.BizException;

/**
 * 通道选择器
 * 
 * @author zhengzhq
 *
 */
public class ChannelSelector {

  private static Logger logger = LoggerFactory.getLogger(ChannelSelector.class);

  private static OrgChannelConfigService orgChannelConfigService =
      ApplicationContextHolder.getBean(OrgChannelConfigService.class);
  private static ChannelService channelService =
      ApplicationContextHolder.getBean(ChannelService.class);

  public static CacheKeyUtil keyUtil = new CacheKeyUtil(ChannelSelector.class);


  /**
   * 获取当前执行的通道，根据配置的权重随机获取一个通道返回
   * 
   * @param orgId 接入方id
   * @param type 通道类型
   * @return
   */
  public static Channel getExecuteChannel(long orgId, int type) {
    return getExecuteChannel(orgId, type, null);
  }

  /**
   * 获取当前执行的通道，根据配置的权重随机获取一个通道返回
   * 
   * @param orgId 接入方id
   * @param type 当前通道类型
   * @param excludeChannels 过滤的通道，被过滤的通道肯定不会返回
   * @return
   */
  public static Channel getExecuteChannel(long orgId, int type, Set<Channel> excludeChannels) {
    List<Channel> channelList = getAvailableChannels(orgId, type);
    // 如果有要排除的通道，那么需要先排除
    if (excludeChannels != null) {
      channelList = removeExcludeChannels(channelList, excludeChannels);
    }
    List<ChannelWeight> channelWeightList =
        ChannelWeightProcessor.getChannelWeightList(channelList);
    // 生成一个随机数
    double randomNum = Math.random();
    for (int i = 0; i < channelWeightList.size(); i++) {
      ChannelWeight channelWeight = channelWeightList.get(i);
      if (channelWeight.getRate() > randomNum) {
        return channelWeight.getChannel();
      }
    }

    throw new BizException(ChannelExceptionEnum.NOT_AVAILABLE_CHANNEL);
  }

  /**
   * 获取通道对应的扩展节点，即要执行的instance
   * 
   * @param extType
   * @param orgId
   * @param type
   * @return
   */
  public static <T> T getChannelExtension(Class<T> extType, long orgId, int type) {
    return getChannelExtension(extType, orgId, type, null);
  }

  public static <T> T getChannelExtension(Class<T> extType, long orgId, int type,
      Set<Channel> excludeChannels) {
    Channel channel = getExecuteChannel(orgId, type, excludeChannels);
    List<T> extList =
        ExtensionLoader.getExtensionList(extType, new String[] {channel.getExtName()});

    if (CollectionUtils.isEmpty(extList)) {
      throw new RuntimeException("can't find extendion by name " + channel.getExtName());
    }

    return extList.get(0);
  }

  /**
   * 获取可用的通道列表，优先从缓存中获取
   * 
   * @param orgId
   * @param type
   * @return
   */
  public static List<Channel> getAvailableChannels(long orgId, int type) {
    return CacheClient.get(keyUtil.getRecKey(".channels." + orgId + "." + type),
        new GetCallback<List<Channel>>() {

          @Override
          public String getCacheGroup() {
            return ChannelService.groupKey;
          }

          @Override
          public List<Channel> getObject() {
            return extractAvailableChannel(orgId, type);
          }
        });
  }

  /**
   * 根据orgId和类型获取可用的通道列表
   * 
   * @param orgId 接入方机构id
   * @param type 通道类型
   * @return
   */
  private static List<Channel> extractAvailableChannel(long orgId, int type) {
    List<OrgChannelConfig> channelConfigList = orgChannelConfigService.get(orgId, type);
    if (CollectionUtils.isEmpty(channelConfigList)) {
      throw new BizException(ChannelExceptionEnum.CHANNEL_NOT_ACTIVED);
    }

    List<Channel> channelList = new ArrayList<Channel>();

    if (isAllChannelCanUse(channelConfigList)) {// 该类型下的全部通道都可用
      ChannelCondition channelCondition = new ChannelCondition(type, StatusType.ENABLE);
      channelList.addAll(channelService.getChannelList(channelCondition));
    } else {// 仅能使用配置的通道部分
      for (OrgChannelConfig orgChannelConfig : channelConfigList) {
        Channel channel = channelService.get(orgChannelConfig.getChannelId());
        Channel newChannel = new Channel();
        BeanUtils.copyProperties(channel, newChannel);
        // 权重设置为配置中的
        newChannel.setWeight(orgChannelConfig.getWeight());
        if (channel.getStatus() == StatusType.ENABLE) {
          channelList.add(newChannel);
        }
      }
    }

    if (CollectionUtils.isEmpty(channelList)) {
      logger.warn("no channel available for orgId {}, type {}", orgId, type);
    }

    return channelList;
  }

  /**
   * 判断是不是该业务下的所有通道都可用
   * 
   * @param channelConfigList
   * @return
   */
  private static boolean isAllChannelCanUse(List<OrgChannelConfig> channelConfigList) {
    if (channelConfigList.size() == 1
        && channelConfigList.get(0).getChannelId() == OrgChannelConfig.ALL_CHANNEL) {
      return true;
    }

    return false;
  }

  public static List<Channel> removeExcludeChannels(List<Channel> availableChannels,
      Set<Channel> excludeChannels) {
    if (CollectionUtils.isEmpty(excludeChannels)) {
      return availableChannels;
    }

    if (CollectionUtils.isEmpty(availableChannels)) {
      return availableChannels;
    }

    Set<Long> channelIdSet = new HashSet<Long>();
    for (Channel excludeChannel : excludeChannels) {
      channelIdSet.add(excludeChannel.getId());
    }

    List<Channel> returnChannels = new ArrayList<Channel>();
    for (Channel channel : availableChannels) {
      if (!channelIdSet.contains(channel.getId())) {
        returnChannels.add(channel);
      }
    }

    return returnChannels;
  }
}
